using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace HadronData
{
    public class Layer
    {
        private String m_strName;
        private Transformation m_trnPlacement;
        private Bounds m_bounds;
        private List<Edge> m_listEdges;
        private LayerNode m_layerNodeRoot;
        private float m_fInitialOrientation;
        private float m_fAngularSpeed;

        public Layer()
        {
            m_strName = "Untitled Layer";
            m_trnPlacement = Transformation.Identity;
            m_bounds = new Bounds();
            m_listEdges = new List<Edge>();
            m_layerNodeRoot = new LayerNode();
            m_fInitialOrientation = 0.0f;
            m_fAngularSpeed = 0.0f;
        }

        public bool Intersects(Edge p_edge)
        {
            Edge edgeTransformed = new Edge(p_edge);
            edgeTransformed.Transform(m_trnPlacement.Inverse);

            return m_layerNodeRoot.Intersects(edgeTransformed);
        }

        public bool Intersects(Ray p_ray)
        {
            return m_layerNodeRoot.Intersects(p_ray.TransformCopy(m_trnPlacement.Inverse));
        }

        public bool Intersects(Ray p_ray, ref Vector2 p_vecIntersectionPoint)
        {
            if (!m_layerNodeRoot.Intersects(p_ray.TransformCopy(m_trnPlacement.Inverse), ref p_vecIntersectionPoint))
                return false;

            p_vecIntersectionPoint = m_trnPlacement.Apply(p_vecIntersectionPoint);
            return true;
        }

        public bool Intersects(Circle p_circle)
        {
            Circle circleTransformed = new Circle(p_circle);
            circleTransformed.Transform(m_trnPlacement.Inverse);

            return m_layerNodeRoot.Intersects(circleTransformed);
        }

        public bool Intersects(Polygon p_polygon)
        {
            Polygon polygonTransformed = new Polygon(p_polygon);
            polygonTransformed.Transform(m_trnPlacement.Inverse);

            return m_layerNodeRoot.Intersects(polygonTransformed);
        }

        public void ComputeContactPoints(Edge p_edge, List<ContactPoint> p_listContactPoints)
        {
            Edge edgeTransformed = new Edge(p_edge);
            edgeTransformed.Transform(m_trnPlacement.Inverse);

            int nListSize = p_listContactPoints.Count;
            m_layerNodeRoot.ComputeContactPoints(edgeTransformed, p_listContactPoints);

            for (int nIndex = nListSize; nIndex < p_listContactPoints.Count; nIndex++)
                p_listContactPoints[nIndex].Transform(m_trnPlacement);
        }

        public void ComputeContactPoints(Circle p_circle, List<ContactPoint> p_listContactPoints)
        {
            Circle circleTransformed = new Circle(p_circle);
            circleTransformed.Transform(m_trnPlacement.Inverse);

            int nListSize = p_listContactPoints.Count;
            m_layerNodeRoot.ComputeContactPoints(circleTransformed, p_listContactPoints);

            for (int nIndex = nListSize; nIndex < p_listContactPoints.Count; nIndex++)
                p_listContactPoints[nIndex].Transform(m_trnPlacement);
        }

        public void ComputeContactPoints(Polygon p_polygon, List<ContactPoint> p_listContactPoints)
        {
            Polygon polygonTransformed = new Polygon(p_polygon);
            polygonTransformed.Transform(m_trnPlacement.Inverse);

            int nListSize = p_listContactPoints.Count;
            m_layerNodeRoot.ComputeContactPoints(polygonTransformed, p_listContactPoints);

            for (int nIndex = nListSize; nIndex < p_listContactPoints.Count; nIndex++)
                p_listContactPoints[nIndex].Transform(m_trnPlacement);
        }

        public void ComputeContactPoints(Geometry p_geometry, List<ContactPoint> p_listContactPoints)
        {
            Geometry geometryLocal = p_geometry.TransformCopy(m_trnPlacement.Inverse);

            int nListSize = p_listContactPoints.Count;
            m_layerNodeRoot.ComputeContactPoints(geometryLocal, p_listContactPoints);

            for (int nIndex = nListSize; nIndex < p_listContactPoints.Count; nIndex++)
                p_listContactPoints[nIndex].Transform(m_trnPlacement);
        }

        public Vector2 ComputeVelocity(Vector2 p_vecPoint)
        {
            Vector2 vecOffset = p_vecPoint - m_trnPlacement.Translation;
            float fPerpDistance = Vector2.Dot(vecOffset, m_trnPlacement.RotationDirection);
            float fSpeed = fPerpDistance * m_fAngularSpeed;
            return new Vector2(
                -m_trnPlacement.RotationDirection.Y,
                m_trnPlacement.RotationDirection.X) * fSpeed;
        }

        public void Reset()
        {
            m_trnPlacement.Rotation = m_fInitialOrientation; 
        }

        public void Update(GameTime p_gameTime)
        {
            m_trnPlacement.Rotation += m_fAngularSpeed * (float) p_gameTime.ElapsedGameTime.TotalSeconds;
        }

        public void Draw(SpriteBatch p_spriteBatch)
        {
            foreach (Edge edge in m_listEdges)
            {
                Edge edgeTransformed = new Edge(edge);
                edgeTransformed.Transform(m_trnPlacement);
                edgeTransformed.Draw(p_spriteBatch);
            }
        }

        public String Name
        {
            get { return m_strName; }
            set { m_strName = value; }
        }

        [ContentSerializerIgnore]
        public Transformation Placement
        {
            get { return m_trnPlacement; }
            set { m_trnPlacement = value; }
        }

        public Vector2 Position
        {
            get { return m_trnPlacement.Translation; }
            set { m_trnPlacement.Translation = value; }
        }

        public float Orientation
        {
            get { return m_trnPlacement.Rotation; }
            set
            {
                m_trnPlacement.Rotation = value;
                m_fInitialOrientation = value;
            }
        }

        public float AngularSpeed
        {
            get { return m_fAngularSpeed; }
            set { m_fAngularSpeed = value; }
        }

        public List<Edge> Edges
        {
            get { return m_listEdges; }
            set
            {
                m_listEdges = value;

                m_bounds.SetEmpty();
                foreach (Edge edge in m_listEdges)
                {
                    m_bounds.ExtendTo(edge.Start);
                    m_bounds.ExtendTo(edge.End);
                }

                List<Edge> listEdges = new List<Edge>();
                foreach (Edge edge in value)
                    listEdges.Add(edge);
                m_layerNodeRoot = LayerNode.PartitionEdges(listEdges);
                m_layerNodeRoot.UpdateBounds();
            }
        }
    }
}
